﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
using System.IO;
using System.Net;
using System.Threading;

namespace System.Web
{
    public delegate void ProcessRequestDelegate(System.Net.HttpListenerContext context);
    public class HttpHandlerServer
    {
        public string webApplicationRoot;
        public string webApplicationBin;
        public readonly bool debug;

        public Dictionary<string, AshxFileData> requestPathToAshxFileData;
        public Dictionary<string, ProcessRequestDelegate> classNameToRequestMethod;

        public Dictionary<string, AspxHeaderData> requestPathToAspxHeaderData;
        Thread httpListenerThread;
        
        public HttpHandlerServer(bool debug,string webApplicationRoot=null,string webApplicationBin=null)
        {
            this.debug = debug;
            if (webApplicationRoot == null)
            {
                this.webApplicationRoot = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot");
            }
            else
            {
                this.webApplicationRoot = webApplicationRoot;
            }
            if (webApplicationBin == null)
            {
                this.webApplicationBin = Path.Combine(webApplicationRoot, "bin");
            }
            else
            {
                this.webApplicationBin = webApplicationBin;
            }
            requestPathToAshxFileData = new Dictionary<string, AshxFileData>(StringComparer.Ordinal);
            requestPathToAspxHeaderData = new Dictionary<string, AspxHeaderData>(StringComparer.Ordinal);
            GetAllHandlerFiles(webApplicationRoot);
            classNameToRequestMethod = new Dictionary<string, ProcessRequestDelegate>(StringComparer.Ordinal);
            GetAllHandlers(webApplicationBin);
        }
        public void Start(string url)
        {
            if (httpListenerThread == null)
            {
                httpListenerThread = new Thread(new ThreadStart(delegate
                {
                    HttpListener httpListener = new HttpListener();
                    httpListener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
                    httpListener.Prefixes.Add(url);
                    httpListener.Start();
                    while (true)
                    {
                        HttpListenerContext httpListenerContext = httpListener.GetContext();
                        string requestPath = httpListenerContext.Request.Url.LocalPath;
                        ProcessHandlerRequest(requestPath, httpListenerContext);
                    }
                }));
            }
            httpListenerThread.Start();
        }
        public void Stop()
        {
            httpListenerThread?.Interrupt();
        }
        public void GetAllAspxData(string webRoot)
        {
            int webRootLength = webRoot.Length;
            string[] aspxFileNames = Directory.GetFiles(webRoot,"*.aspx");
            string requestPath;
            AspxHeaderData aspxHeaderData;
            foreach (var aspxFileName in aspxFileNames)
            {
                if (File.Exists(aspxFileName))
                {
                    aspxHeaderData = new AspxHeaderData(aspxFileName);
                    requestPath = "/" + aspxFileName.Substring(webRootLength);
                    requestPathToAspxHeaderData.Add(requestPath, aspxHeaderData);
                }
            }
        }
        public void GetAllHandlerFiles(string webRoot)
        {
            int webRootLength = webRoot.Length;
            
            string requestPath;
            AshxFileData ashxFileData = null;
            string[] ashxFileNames = Directory.GetFiles(webRoot, "*.ashx",SearchOption.AllDirectories);
            foreach (var ashxFileName in ashxFileNames)
            {
                if (File.Exists(ashxFileName))
                {
                    ashxFileData = new AshxFileData(ashxFileName);
                    requestPath ="/" + ashxFileName.Substring(webRootLength);
                    requestPathToAshxFileData.Add(requestPath, ashxFileData);
                }
            }
            AspxHeaderData aspxHeaderData = null;
            string[] aspxFileNames = Directory.GetFiles(webRoot, "*.aspx",SearchOption.AllDirectories);
            foreach (var aspxFileName in aspxFileNames)
            {
                if (File.Exists(aspxFileName))
                {
                    aspxHeaderData = new AspxHeaderData(aspxFileName);
                    requestPath = "/" + aspxFileName.Substring(webRootLength);
                    requestPathToAspxHeaderData.Add(requestPath, aspxHeaderData);
                }
            }
        }
        
        public void GetAllHandlers(string dllFolder)
        {
            if (!Directory.Exists(dllFolder))
            {
                dllFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin");
                if (!Directory.Exists(dllFolder))
                {
                    Directory.CreateDirectory(dllFolder);
                }
            }
            string[] assemblyFileNames = Directory.GetFiles(dllFolder, "*.dll");
            Type[] types = null;
            Type[] interfaceTypes = null;
            Type type;
            string assemblyFileName;
            for (int i=0;i< assemblyFileNames.Length;i++)
            {
                assemblyFileName = assemblyFileNames[i];
                string assemblyShortFileName = Path.GetFileName(assemblyFileName);
                if (assemblyShortFileName == "Cassini.dll" 
                    || assemblyShortFileName== "System.Web.dll")
                {
                    continue;
                }
                var assembly = Assembly.LoadFrom(assemblyFileName);
                types = assembly.GetTypes();
                for(int j=0;j<types.Length;j++)
                {
                    type = types[j];
                    if (typeof(System.Web.IHttpHandler).IsAssignableFrom(type))
                    {
                        ProcessRequestDelegate processRequestDelegate = GetProcessRequestDelegate(type);
                        classNameToRequestMethod.Add(type.FullName, processRequestDelegate);
                    }
                }
            }
        }
        public ProcessRequestDelegate GetProcessRequestDelegate(Type handlerType)
        {
            MethodInfo initMethodInfo= handlerType.GetMethod("Init", Type.EmptyTypes);
            
            ConstructorInfo handlerConstructorInfo= handlerType.GetConstructor(Type.EmptyTypes);
            ConstructorInfo httpContextConstructorInfo = typeof(System.Web.HttpContext)
                .GetConstructor(new Type[] { typeof(System.Net.HttpListenerContext) });
            MethodInfo processReuestMethodInfo = handlerType.GetMethod("ProcessRequest",new Type[] { typeof(System.Web.HttpContext)});
            DynamicMethod dynamicMethod = new DynamicMethod(handlerType.FullName, typeof(void), new Type[] { typeof(System.Net.HttpListenerContext) });
            ILGenerator iLGenerator= dynamicMethod.GetILGenerator();

            if (initMethodInfo != null)
            {
                var handler = iLGenerator.DeclareLocal(handlerType);
                iLGenerator.Emit(OpCodes.Newobj, handlerConstructorInfo);
                iLGenerator.Emit(OpCodes.Stloc, handler);

                //if (hasPageLoadMethod)
                //{
                //    iLGenerator.Emit(OpCodes.Ldloc, handler);
                //    iLGenerator.Emit(OpCodes.Ldloc, handler);
                //    iLGenerator.Emit(OpCodes.Ldftn,pageLoadMethodInfo);
                //    iLGenerator.Emit(OpCodes.Newobj,pageEventConstructor);
                //    iLGenerator.Emit(OpCodes.Call, add_page_Load_EventMethodInfo);
                //}
                iLGenerator.Emit(OpCodes.Ldloc, handler);
                iLGenerator.Emit(OpCodes.Callvirt, initMethodInfo);
                iLGenerator.Emit(OpCodes.Ldloc, handler);
            }
            else
            {
                iLGenerator.Emit(OpCodes.Newobj, handlerConstructorInfo);
            }
            iLGenerator.Emit(OpCodes.Ldarg_0);
            iLGenerator.Emit(OpCodes.Newobj, httpContextConstructorInfo);
            iLGenerator.Emit(OpCodes.Callvirt, processReuestMethodInfo);
            iLGenerator.Emit(OpCodes.Ret);
            ProcessRequestDelegate processRequestDelegate = null;
            processRequestDelegate =(ProcessRequestDelegate)dynamicMethod.CreateDelegate(typeof(ProcessRequestDelegate));
            return processRequestDelegate;
        }
        public bool ProcessHandlerRequest(string requestPath,System.Net.HttpListenerContext context)
        {
            context.Response.StatusCode = 200;
            context.Response.ContentType = "text/html; charset=utf-8";
            AshxFileData ashxFileData;
            AspxHeaderData aspxHeaderData;
            string className=null;
            if (requestPathToAshxFileData.TryGetValue(requestPath, out ashxFileData))
            {
                className = ashxFileData.Class;
            }
            if (requestPathToAspxHeaderData.TryGetValue(requestPath, out aspxHeaderData))
            {
                className = aspxHeaderData.Inherits;
            }
            if(className!=null)
            {
                ProcessRequestDelegate processRequestDelegate;
                if (classNameToRequestMethod.TryGetValue(className, out processRequestDelegate))
                {
                    if (debug)
                    {
                        processRequestDelegate(context);
                        context.Response.Close();
                    }
                    else
                    {
                        try
                        {
                            processRequestDelegate(context);
                            context.Response.Close();
                        }
                        catch (Exception ex)
                        {
                            context.Response.Write(ex.Message);
                            context.Response.StatusCode = 500;
                            context.Response.Close();
                        }
                    }
                    return true;
                }
                else
                {
                    context.Response.Write($"错误!找不到对应的类:{className}");
                    context.Response.Close();
                    return false;
                }
            }
            else
            {
                context.Response.Write($"错误!找不到相应的请求文件:{requestPath}");
                context.Response.Close();
                return false;
            }
        }
        public bool ProcessStaticRequest(string requestPath,System.Net.HttpListenerContext context)
        {
            if (requestPath.Length == 1)
            {
                context.Response.Write("Hello!");
                context.Response.Close();
                return true;
            }
            else
            {
                string fileName = Path.Combine(webApplicationRoot, requestPath);
                if (File.Exists(fileName))
                {
                    context.Response.WriteFile(fileName);
                    context.Response.Close();
                }
                else
                {
                    context.Response.StatusCode = 403;
                    context.Response.Close();
                }
                return true;
            }
        }
        public bool ProcessRequest(string requestPath,System.Net.HttpListenerContext context)
        {
            bool result = false;
            if (debug)
            {
                if (requestPath.EndsWith(".ashx") || requestPath.EndsWith(".aspx"))
                {
                    result = ProcessHandlerRequest(requestPath, context);
                }
                else
                {
                    result = ProcessStaticRequest(requestPath, context);
                }
                return result;
            }
            else
            {
                try
                {
                    if (requestPath.EndsWith(".ashx") || requestPath.EndsWith(".aspx"))
                    {
                        result = ProcessHandlerRequest(requestPath, context);
                    }
                    else
                    {
                        result = ProcessStaticRequest(requestPath, context);
                    }
                }
                catch (Exception ex)
                {

                }
                return result;
            }
        }
    }
}
